﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using Antlr.StringTemplate;
using System.Text.RegularExpressions;
using System.Xml.Serialization;

namespace MGenScope
{
    [Serializable]
    public class EngineDriver:INotifyPropertyChanged
    {
        TestPhase phase;
        Dictionary<string, DivideResults> divideResults = new Dictionary<string, DivideResults>();
        
        Process process;
     
        DateTime lastReceived;
        [XmlIgnore]
        public IDictionary<string, DivideResults> Results
        {
            get { return divideResults; }
        }
        [XmlIgnore]
        public string CurrentFen { get { return currFen;} }
        
        [XmlIgnore]
        public ILog Log { get; set; }
        void ConsumeStdOut()
        {
            do
            {
                Application.DoEvents();
            }
            while ((DateTime.Now - lastReceived).TotalMilliseconds < 1000);
        }
       
        void SendToStdIn(string toSend)
        {
            string[] lines = toSend.Split(new char[]{'\n'},StringSplitOptions.RemoveEmptyEntries);
            foreach (string line in lines)
            {
                process.StandardInput.WriteLine(line);
                Log.LogEngineMessage(line);
                ConsumeStdOut();
            }
        }
        public void Run()
        {
            phase = TestPhase.Initial;
            
            if (null != process && !process.HasExited )
                process.Kill();
            Log.LogEngineMessage("Starting: " + Path.GetFileNameWithoutExtension(exePath));
            process = new Process();
            process.StartInfo = new ProcessStartInfo();
            process.StartInfo.WorkingDirectory = EngineSettings.WorkingFolder;
            process.StartInfo.FileName = exePath;
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.CreateNoWindow = true;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardInput = true;
            process.StartInfo.RedirectStandardError = true;
            process.OutputDataReceived += new DataReceivedEventHandler(process_OutputDataReceived);
            process.ErrorDataReceived += new DataReceivedEventHandler(process_OutputDataReceived);
            lastReceived = DateTime.Now;
            process.Start();
            process.BeginErrorReadLine();
            process.BeginOutputReadLine();
            ConsumeStdOut();
            
            if (!string.IsNullOrEmpty(EngineSettings.InitTemplate))
            {
                process.StandardInput.Write(ApplyTemplate(EngineSettings.InitTemplate));
            }
            ConsumeStdOut();
            
            if (!string.IsNullOrEmpty(EngineSettings.SetInitialPositionTemplate))
            {
                SendToStdIn(ApplyTemplate(EngineSettings.SetInitialPositionTemplate));
            }
            ConsumeStdOut();
            SendToStdIn(ApplyTemplate(EngineSettings.ForceTemplate));
            ConsumeStdOut();
        }
        int currentDepth;
        string currentFen;
        public int Depth { get { return currentDepth; } }
        public void Divide(int depth,string Fen)
        {
            currentDepth = depth;
            currentFen = Fen;
            SendToStdIn(ApplyTemplate(EngineSettings.SetFenTemplate,"fen",Fen));
            ConsumeStdOut();
            phase = TestPhase.GetFen;
            SendToStdIn(ApplyTemplate(EngineSettings.GetFenTemplate));
            ConsumeStdOut();
        }
        string fenBeforeMove;
        public string FenBeforeMove { get { return fenBeforeMove; } }
        public void Divide(int depth, string Fen,string move)
        {
            currentDepth = depth;
            fenBeforeMove = currentFen = Fen;
            SendToStdIn(ApplyTemplate(EngineSettings.SetFenTemplate, "fen", Fen));
            ConsumeStdOut();
            SendToStdIn(ApplyTemplate(EngineSettings.DoMoveTemplate, "move", move));
            ConsumeStdOut();
            phase = TestPhase.GetFen;
            SendToStdIn(ApplyTemplate(EngineSettings.GetFenTemplate));
            ConsumeStdOut();
        }

        private string ApplyTemplate(string template, string varName, object depth)
        {
            StringTemplate st = new StringTemplate(template);
            st.SetAttribute(varName, depth.ToString());
            return st.ToString();
        }
        private string ApplyTemplate(string p)
        {
            StringTemplate st = new StringTemplate(p);
            return st.ToString();
        }
        string currFen;
        public event EventHandler Complete;
        void BeginDivide()
        {
            divideResults.Clear();
            phase = TestPhase.Divide;
            SendToStdIn(ApplyTemplate(EngineSettings.DivideTemplate, "depth", currentDepth));
        }
        void process_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            lastReceived = DateTime.Now;
            if (e.Data != null)
            {
                if (null != Log)
                    Log.LogEngineMessage(e.Data);
                switch (phase)
                {
                    case TestPhase.Divide:
                        if (Regex.IsMatch(e.Data, EngineSettings.EndDivideRegex))
                        {
                            if (Complete != null)
                            {
                                Complete(this, EventArgs.Empty);
                            }
                            break;
                        }
                        int count = 0;
                        Regex rcount = new Regex(EngineSettings.ExtractMovesCountRegex);
                        Match match = rcount.Match(e.Data);
                        if (match.Success && match.Groups.Count >= 2)
                        {
                            count = int.Parse(match.Groups[1].Value);
                        }
                        Regex rmove = new Regex(EngineSettings.ExtractMoveRegex);
                        match = rmove.Match(e.Data);
                        string move = null;
                        if (match.Success && match.Groups.Count >= 2)
                        {
                            move = match.Groups[1].Value;
                        }
                        if (move != null)
                        {
                            DivideResults res = new DivideResults();
                            res.Count = count;
                            res.Move = move;
                            res.Fen = currFen;
                            res.Depth = currentDepth;
                            divideResults[move.ToLower()] = res;
                        }
                        break;
                    case TestPhase.GetFen:
                        Regex rfen = new Regex(EngineSettings.FenRegex);
                        match = rfen.Match(e.Data);
                        if (match.Success && match.Groups.Count >= 2)
                        {
                            currFen = match.Groups[1].Value;
                        }
                        if (string.IsNullOrEmpty(EngineSettings.FenEndRegex.Trim()))
                        {
                            BeginDivide();
                        }
                        else
                        {
                            phase = TestPhase.WaitEndFen;
                        }
                        break;
                    case TestPhase.WaitEndFen:
                        Regex rfenend = new Regex(EngineSettings.FenEndRegex);
                        match = rfenend.Match(e.Data);
                        if (match.Success && match.Groups.Count >= 2)
                        {
                            BeginDivide();
                        }
                        break;
                }
            }
        }

        
        public EngineDriver()
        {
            EngineSettings = new EngineSettings();
        }
        string exePath;
        public string ExePath
        {
            get
            {
                return exePath;
            }
            set
            {
                exePath = value;
                if (null != PropertyChanged)
                    PropertyChanged(this, new PropertyChangedEventArgs("ExePath"));
            }
        }
        public EngineSettings EngineSettings { get; set; }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        internal void Kill()
        {
            if (null != process)
            {
                if( !process.HasExited )
                    process.Kill();
                if( !process.HasExited )
                    process = null;
            }
        }
    }
}
